package com.diy.sigmund.diyorder.test.controller;

import com.diy.sigmund.diycommon.entity.ResultMsg;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author ylm-sigmund
 * @since 2021/12/31 14:08
 */
@RestController
@RequestMapping("")
public class CuratorController {

    @PostMapping("/curatorController/client")
    public Object client() throws Exception {
        CuratorFramework client = createClient();
        // 创建数据节点
        // Zookeeper的节点创建模式：
        //
        // PERSISTENT：持久化
        // PERSISTENT_SEQUENTIAL：持久化并且带序列号
        // EPHEMERAL：临时
        // EPHEMERAL_SEQUENTIAL：临时并且带序列号

        // 创建一个节点，初始内容为空
        client.create().forPath("/path");
        // 创建一个节点，附带初始化内容
        client.create().forPath("/path", "init".getBytes());
        // 创建一个节点，指定创建模式（临时节点），内容为空
        client.create().withMode(CreateMode.EPHEMERAL).forPath("/path");
        // 创建一个节点，指定创建模式（临时节点），附带初始化内容
        client.create().withMode(CreateMode.EPHEMERAL).forPath("/path", "init".getBytes());
        // 创建一个节点，指定创建模式（临时节点），附带初始化内容，并且自动递归创建父节点
        client.create()
                .creatingParentContainersIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath("/path", "init".getBytes());
        // 这个creatingParentContainersIfNeeded()接口非常有用，
        // 因为一般情况开发人员在创建一个子节点必须判断它的父节点是否存在，
        // 如果不存在直接创建会抛出NoNodeException，
        // 使用creatingParentContainersIfNeeded()之后Curator能够自动递归创建所有所需的父节点。

        // 删除数据节点
        // 删除一个节点 注意，此方法只能删除叶子节点，否则会抛出异常。
        client.delete().forPath("/path");
        // 删除一个节点，并且递归删除其所有的子节点
        client.delete().deletingChildrenIfNeeded().forPath("/path");
        // 删除一个节点，强制指定版本进行删除
        client.delete().withVersion(10086).forPath("/path");
        // 删除一个节点，强制保证删除
        // guaranteed()接口是一个保障措施，只要客户端会话有效，那么Curator会在后台持续进行删除操作，直到删除节点成功。
        client.delete().guaranteed().forPath("/path");

        // 读取数据节点数据
        // 读取一个节点的数据内容
        client.getData().forPath("/path");
        // 注意，此方法返的返回值是byte[ ];

        // 读取一个节点的数据内容，同时获取到该节点的stat
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath("/path");
        // 更新数据节点数据
        // 更新一个节点的数据内容
        client.setData().forPath("/path", "data".getBytes());
        // 注意：该接口会返回一个Stat实例

        // 更新一个节点的数据内容，强制指定版本进行更新
        client.setData().withVersion(10086).forPath("/path", "data".getBytes());
        // 检查节点是否存在
        client.checkExists().forPath("/path");
        // 注意：该方法返回一个Stat实例，用于检查ZNode是否存在的操作. 可以调用额外的方法(监控或者后台处理)并在最后调用forPath( )指定要操作的ZNode

        // 获取某个节点的所有子节点路径
        client.getChildren().forPath("/path");

        // 事务
        // CuratorFramework的实例包含inTransaction( )接口方法，调用此方法开启一个ZooKeeper事务. 可以复合create, setData, check, and/or delete 等操作然后调用commit()作为一个原子操作提交。一个例子如下：
        client.inTransaction().check().forPath("/path")
                .and()
                .create().withMode(CreateMode.EPHEMERAL).forPath("/path", "data".getBytes())
                .and()
                .setData().withVersion(10086).forPath("/path", "data2".getBytes())
                .and()
                .commit();

        // 一个异步创建节点的例子如下：
        Executor executor = Executors.newFixedThreadPool(2);
        client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .inBackground((curatorFramework, curatorEvent) -> System.out.println(
                        String.format("eventType:%s,resultCode:%s", curatorEvent.getType(),
                                curatorEvent.getResultCode())), executor)
                .forPath("/path");
        // 注意：如果#inBackground()方法不指定executor，那么会默认使用Curator的EventThread去进行异步处理。
        return ResultMsg.success();
    }

    /**
     * <br>作者：zhrowable
     * <br>链接：https://www.jianshu.com/p/70151fc0ef5d
     * <br>来源：简书
     * <br>著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
     *
     * @return CuratorFramework
     */
    private CuratorFramework createClient() {
        String connectString = "127.0.0.1:2181";
        // String connectString = "127.0.0.1:7999";
        // 1.使用静态工程方法创建客户端

        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client =
                CuratorFrameworkFactory.newClient(
                        connectString,
                        5000,
                        3000,
                        retryPolicy);

        // newClient静态工厂方法包含四个主要参数：
        //
        // 参数名	说明
        // connectionString	服务器列表，格式host1:port1,host2:port2,...
        // retryPolicy	重试策略,内建有四种重试策略,也可以自行实现RetryPolicy接口
        // sessionTimeoutMs	会话超时时间，单位毫秒，默认60000ms
        // connectionTimeoutMs	连接创建超时时间，单位毫秒，默认60000ms

        // 2.使用Fluent风格的Api创建会话

        // RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        // CuratorFramework client =
        //         CuratorFrameworkFactory.builder()
        //                 .connectString(connectString)
        //                 .sessionTimeoutMs(5000)
        //                 .connectionTimeoutMs(5000)
        //                 .retryPolicy(retryPolicy)
        //                 .build();

        // 3.创建包含隔离命名空间的会话
        // 为了实现不同的Zookeeper业务之间的隔离，需要为每个业务分配一个独立的命名空间（NameSpace），
        // 即指定一个Zookeeper的根路径（官方术语：为Zookeeper添加“Chroot”特性）。
        // 例如（下面的例子）当客户端指定了独立命名空间为“/base”，那么该客户端对Zookeeper上的数据节点的操作都是基于该目录进行的。
        // 通过设置Chroot可以将客户端应用与Zookeeper服务端的一课子树相对应，在多个应用共用一个Zookeeper集群的场景下，
        // 这对于实现不同应用之间的相互隔离十分有意义。

        // RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        // CuratorFramework client =
        //         CuratorFrameworkFactory.builder()
        //                 .connectString(connectString)
        //                 .sessionTimeoutMs(5000)
        //                 .connectionTimeoutMs(5000)
        //                 .retryPolicy(retryPolicy)
        //                 .namespace("base")
        //                 .build();

        // 启动客户端
        // 当创建会话成功，得到client的实例然后可以直接调用其start( )方法：
        client.start();
        return client;
    }
}
